#include <Ape/MT/Thread.h>

#include <Ape/Exception.h>
#include <Ape/Collections/CollectionHelpers.h>
#include <unistd.h>
#include <Ape/EIndexOutOfRange.h>

namespace Ape {
	namespace MT {
		using namespace Ape;
		using namespace Ape::Collections;
		using namespace Ape::EventModel;
		
		static Map<Uuid, Thread*> s_Threads;
		static Map<void*, Thread*> s_ThreadsByHandle;
		
		void* thread_dispatch (void* thr_id) {
			Uuid threadID = TypedHelper<Uuid>::ToStack (thr_id);
			TypedHelper<Uuid>::Free (thr_id);
			thr_id = NULL;
			
			Thread* thr = s_Threads[threadID];
			thr->__Start ( (void*) thr->m_Handle);
			
			return NULL;
		}
		/*
		Thread::Thread()
			: ID(Uuid::Generate()),
			  Name(L"Thread-{" + ID.ToString() + L"}"),
			  m_Exception(NULL)
			  #if defined(APE_USES_PTHREAD)
			  ,m_Handle(NULL)
			  #endif
		{
			s_Threads[ID] = this;
		}
		*/
		
		const Ape::Uuid& Thread::get_ID() const {
			return ID;
		}
		
		Thread* Thread::ByID(const Ape::Uuid& id) {
			if (s_Threads.KeyExists(id)) {
				return s_Threads[id];
			}
			throw Ape::EIndexOutOfRange("No thread with the specified id: " + id.ToString());
		}
		
		Thread::Thread (const String name)
				: ID (Uuid::Generate() ),
				Name (name),
				m_Exception (NULL)
#if defined(APE_USES_PTHREAD)
				, m_Handle (NULL)
#endif
				, m_Args (NULL)
				, m_Running (false)
		{
			s_Threads[ID] = this;
		}
		
		Thread::~Thread() {
			Join();
			s_Threads.RemoveAt (ID);
			del (m_Exception);
		}
		
		void Thread::__Start (void* handle) {
			try {
				m_Running = true;
				s_ThreadsByHandle[ (void*) handle] = this;
				if (!m_Args) {
					EventArgs ea (this, 0);
					OnStart (ea);
				}
				else {
					OnStart (*m_Args);
					delete m_Args;
				}
			}
			catch (Exception& e) {
				m_Exception = e.Clone();
			}
			try {
				s_ThreadsByHandle.RemoveAt ( (void*) m_Handle);
			}
			catch (Exception&) {}
			m_Running = false;
		}
		
		bool Thread::IsRunning() const {
			return m_Running;
		}
		
		void Thread::Start() {
			if (m_Running)
				throw Exception (L"Thread is already running");
				
			void* thr_id = TypedHelper<Uuid>::ToHeap (ID);
			
#if defined(APE_USES_PTHREAD)
			pthread_create (&m_Handle, NULL, thread_dispatch, thr_id);
#else
#warning No threads support is turned on: using debug-mode
			thread_dispatch (thr_id);
#endif
			
		}
		
		void Thread::Start (EventArgs& ea) {
			if (m_Running)
				throw Exception (L"Thread is already running");
			m_Args = new EventArgs (ea);
			Start();
		}
		
		void Thread::Join() {
			// TODO
#if defined(APE_USES_PTHREAD)
			void* thr_return = NULL;
			pthread_join (m_Handle, &thr_return);
#else
#warning No threads support is turned on: using debug-mode
#endif
		}
		
		const Ape::String Thread::ToString() const {
			return L"Thread [ID:" + ID.ToString() + L"; Name:" + Name + L"]";
		}
		
		Exception* Thread::get_Exception() const {
			return m_Exception;
		}
		
		bool Thread::IsOk() const {
			return !m_Exception;
		}
		
		Thread* Thread::Current() {
#if defined(APE_USES_PTHREAD)
			pthread_t hPThread = pthread_self();
			Thread* thr = s_ThreadsByHandle[ (void*) hPThread];
			return thr;
#else
			throw Exception ("Oops... threads support is turned off");
#endif
		}
		
		void Thread::Sleep(size_t microSecs) {
			usleep(microSecs);
		}
	}
}
